WithdrawWidget.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. "use client";
  2. import { GameListRep } from "@/api/home";
  3. import { userInfoApi } from "@/api/login";
  4. import { Wallet } from "@/api/user";
  5. import { ChannelType, getWithDrawApi, WithDrawParams, WithDrawType } from "@/api/withdraw";
  6. import { clearWallet } from "@/app/[locale]/(navbar)/withdraw/actions";
  7. import Box from "@/components/Box";
  8. import ButtonOwn from "@/components/ButtonOwn";
  9. import Empty from "@/components/Empty";
  10. import MobileField from "@/components/Fields/MobileField";
  11. import TipsModal, { ModalProps } from "@/components/TipsModal";
  12. import useGame from "@/hooks/useGame";
  13. import { useSystemStore } from "@/stores/useSystemStore";
  14. import { useUserInfoStore } from "@/stores/useUserInfoStore";
  15. import { useWalletStore } from "@/stores/useWalletStore";
  16. import { isEmail } from "@/utils";
  17. import { flatPoint, percentage } from "@/utils/methods";
  18. import { ActionSheet, Button, Form, Input, ProgressBar, Toast } from "antd-mobile";
  19. import { FormInstance } from "antd-mobile/es/components/form";
  20. import { useTranslations } from "next-intl";
  21. import Link from "next/link";
  22. import { FC, useRef, useState } from "react";
  23. import { Swiper, SwiperSlide } from "swiper/react";
  24. import "./page.scss";
  25. interface Props {
  26. channels: WithDrawType[];
  27. wallet: Wallet;
  28. playlist?: GameListRep[];
  29. }
  30. export enum ChannelEnum {
  31. CPF = 1,
  32. Email,
  33. Phone,
  34. CNPJ,
  35. }
  36. type FieldValueType = {
  37. account_no: string;
  38. channel_id: (typeof ChannelEnum)[keyof typeof ChannelEnum];
  39. type: number;
  40. code_phone?: string;
  41. };
  42. interface MobileFieldProps {
  43. value?: FieldValueType;
  44. onChange?: (value: FieldValueType) => void;
  45. actions: Array<ChannelType & { text: string; key: number }>;
  46. }
  47. const PixField: FC<MobileFieldProps> = (props) => {
  48. const {
  49. actions,
  50. value = { account_no: "", channel_id: actions[0].id, type: actions[0].type },
  51. onChange,
  52. } = props;
  53. let [visible, setVisible] = useState(false);
  54. const t = useTranslations("WithdrawPage");
  55. let [prefix, setPrefix] = useState<ChannelType & { text: string; key: number }>(actions[0]);
  56. let [placeholder, setPlaceholder] = useState("000.000.000-00");
  57. const onAction = (item: any) => {
  58. setPrefix(item);
  59. onRealValueChange("");
  60. if (item.type === ChannelEnum.CPF) {
  61. setPlaceholder("000.000.000-00");
  62. return;
  63. }
  64. if (item.type === ChannelEnum.CNPJ) {
  65. setPlaceholder("00.000.000.0000-00");
  66. return;
  67. }
  68. if (item.type === ChannelEnum.Phone) {
  69. setPlaceholder("11 dígitos");
  70. return;
  71. }
  72. if (item.type === ChannelEnum.Email) {
  73. setPlaceholder("Email");
  74. return;
  75. }
  76. };
  77. const onRealValueChange = (value: string) => {
  78. console.log(`🚀🚀🚀🚀🚀-> in WithdrawWidget.tsx on 80`, value);
  79. // console.log('prefix', prefix)
  80. let account_no = value;
  81. if (prefix.type === 1) {
  82. account_no = value
  83. .replace(/[^0-9]/g, "")
  84. .replace(/[\-]/g, "")
  85. .slice(0, 11);
  86. // console.log('account_no', value.replace(/\D/g, ''))
  87. // account_no = account_no.slice(0,3)
  88. }
  89. if (onChange) {
  90. onChange({ account_no, channel_id: prefix.id, type: prefix.type });
  91. // console.log('value', props.value)
  92. }
  93. };
  94. const mobileChange = (values: any) => {
  95. if (onChange) {
  96. onChange({
  97. account_no: values.realValue,
  98. channel_id: prefix.id,
  99. type: prefix.type,
  100. code_phone: values.preValue,
  101. });
  102. }
  103. };
  104. return (
  105. <>
  106. <div className="flex">
  107. <div onClick={() => setVisible(true)} className={"mr-[0.1rem] flex flex-shrink-0"}>
  108. <span className={"mr-[0.1rem]"}>{prefix.text}</span>
  109. <span className="iconfont icon-xialaxuanze"></span>
  110. </div>
  111. {prefix.type === ChannelEnum.Phone ? (
  112. <MobileField
  113. value={{ preValue: "55", realValue: value.account_no }}
  114. onChange={mobileChange}
  115. />
  116. ) : (
  117. <Input
  118. name="secretKey"
  119. value={value.account_no}
  120. placeholder={placeholder}
  121. onChange={onRealValueChange}
  122. />
  123. )}
  124. </div>
  125. <ActionSheet
  126. extra=""
  127. cancelText={t("Cancelar")}
  128. visible={visible}
  129. actions={actions}
  130. style={{ background: "#fff" }}
  131. closeOnAction={true}
  132. getContainer={null}
  133. onAction={onAction}
  134. onClose={() => setVisible(false)}
  135. />
  136. </>
  137. );
  138. };
  139. const WithdrawWidget: FC<Props> = (props) => {
  140. const t = useTranslations();
  141. const { channels, wallet } = props;
  142. const { getGameUrl } = useGame();
  143. const isStrictMode = useSystemStore((state) => state.identity_verify.withdraw === 1);
  144. const score = useWalletStore((state) => state.score)!;
  145. // 彩金
  146. const withdrawRef = useRef<ModalProps>(null);
  147. const bounsModalRef = useRef<ModalProps>(null);
  148. // 积分
  149. const scoreRef = useRef<ModalProps>(null);
  150. // 未完成游戏
  151. const gameModelRef = useRef<ModalProps>(null);
  152. const game = useRef<GameListRep | null>(null);
  153. // 表单
  154. const formRef = useRef<FormInstance>(null);
  155. // 是否能提现
  156. const [activeWallet, setActiveWallet] = useState<WithDrawType>(channels[0]);
  157. const walletAction =
  158. activeWallet &&
  159. activeWallet.channels?.map((item) => ({
  160. text: ChannelEnum[item.type],
  161. key: item.id,
  162. ...item,
  163. }));
  164. const userInfo = useUserInfoStore((state) => state.userInfo);
  165. const initParams = {
  166. channel: "",
  167. amount: "",
  168. passport: userInfo.passport,
  169. user_name: userInfo.user_name,
  170. };
  171. const paramsTarget = useRef<WithDrawParams | null>(null);
  172. const AmountValidator = (rules: any, value: string) => {
  173. const num = +value;
  174. if (num > activeWallet.max_amount) {
  175. return Promise.reject(
  176. new Error(t("WithdrawPage.amountReg", { max: activeWallet.max_amount }))
  177. );
  178. }
  179. if (score && num > score) {
  180. return Promise.reject(new Error(t("WithdrawPage.amountMaxReg")));
  181. }
  182. return Promise.resolve();
  183. };
  184. const ChannelValidator = (rules: any, value: FieldValueType) => {
  185. if (!value.account_no) return Promise.reject(new Error(t("WithdrawPage.channel")));
  186. if (value.type === ChannelEnum.CPF) {
  187. return value.account_no.length !== 11
  188. ? Promise.reject(new Error(t("WithdrawPage.cpfReg")))
  189. : Promise.resolve();
  190. }
  191. if (value.type === ChannelEnum.CNPJ) {
  192. return value.account_no.length !== 14
  193. ? Promise.reject(new Error(t("WithdrawPage.cnpjReg")))
  194. : Promise.resolve();
  195. }
  196. if (value.type === ChannelEnum.Email) {
  197. return isEmail(value.account_no)
  198. ? Promise.resolve()
  199. : Promise.reject(new Error(t("WithdrawPage.EmailReg")));
  200. }
  201. if (value.type === ChannelEnum.Phone) {
  202. return value.account_no.length < 10
  203. ? Promise.reject(new Error(t("WithdrawPage.phoneReg")))
  204. : Promise.resolve();
  205. }
  206. return Promise.resolve();
  207. };
  208. const onFinish = async (value: any) => {
  209. const params = { ...value, ...value.channel, amount: +value.amount };
  210. // 如果是电话号码,拼接区号
  211. if (params.code_phone) {
  212. params.account_no = `${params.code_phone}${params.account_no}`;
  213. }
  214. delete params.channel;
  215. const { data } = await userInfoApi();
  216. // 如果有未完成游戏
  217. if (data.play_list && data.play_list.length > 0) {
  218. game.current = data.play_list[0];
  219. gameModelRef.current?.onOpen();
  220. return;
  221. }
  222. // 如果彩金 || 彩金打码量不为0
  223. if (
  224. flatPoint(wallet.target_point_rollover - wallet.current_point_rollover) > 0 ||
  225. (wallet.point || 0) > 0
  226. ) {
  227. paramsTarget.current = params;
  228. bounsModalRef.current?.onOpen();
  229. return;
  230. }
  231. extractHandler(params);
  232. };
  233. const extractHandler = async (params: WithDrawParams) => {
  234. const withResult = await getWithDrawApi(params).catch((error) => {
  235. Toast.show(t(`code.${error.data.code}`));
  236. });
  237. if (withResult && withResult.code === 200) {
  238. Toast.show(t("code.200"));
  239. }
  240. await clearWallet();
  241. };
  242. const goGame = () => {
  243. const current = game.current;
  244. getGameUrl(current!, { id: current?.id + "" });
  245. };
  246. if (!activeWallet) return <Empty />;
  247. return (
  248. <>
  249. <Box className={"custom-form"}>
  250. <div className="withdraw-box">
  251. <div className="img-box">
  252. <img
  253. className={"h-[100%] w-[100%] object-cover"}
  254. src={activeWallet.icon}
  255. alt={activeWallet.name}
  256. width={160}
  257. height={40}
  258. />
  259. </div>
  260. {/*<div className={"mb-[0.1rem] flex flex-wrap gap-[0.0347rem]"}>*/}
  261. {/* {channels.map((item) => {*/}
  262. {/* return (*/}
  263. {/* <Fragment key={item.id}>*/}
  264. {/* <p*/}
  265. {/* className={`btn-box ${activeWallet.id === item.id ? "active" : ""}`}*/}
  266. {/* onClick={() => {*/}
  267. {/* formRef.current?.resetFields();*/}
  268. {/* setActiveWallet(item);*/}
  269. {/* }}*/}
  270. {/* >*/}
  271. {/* {item.name}*/}
  272. {/* </p>*/}
  273. {/* </Fragment>*/}
  274. {/* );*/}
  275. {/* })}*/}
  276. {/*</div>*/}
  277. <Swiper slidesPerView={"auto"} className={"mb-[0.1rem]"} centeredSlidesBounds>
  278. {channels?.map((item, index) => (
  279. <SwiperSlide key={item.id} className={"max-w-fit"}>
  280. <p
  281. className={`btn-box truncate ${activeWallet.id === item.id ? "active" : ""}`}
  282. onClick={() => {
  283. formRef.current?.resetFields();
  284. setActiveWallet(item);
  285. }}
  286. >
  287. {item.name}
  288. </p>
  289. </SwiperSlide>
  290. ))}
  291. </Swiper>
  292. <h1>{t("WithdrawPage.Certifique")}</h1>
  293. <p className={"text-[0.1rem] text-[#3bc117]"}>{t("WithdrawPage.keyTips")}</p>
  294. <ul className={"ml-[0.11rem] list-decimal text-[0.1rem]"}>
  295. <li>
  296. {t("WithdrawPage.rulesRange", {
  297. min: activeWallet.min_amount,
  298. max: activeWallet.max_amount,
  299. })}
  300. </li>
  301. <li>
  302. {t("WithdrawPage.rulesToll", {
  303. toll: activeWallet.fee_rate,
  304. })}
  305. </li>
  306. </ul>
  307. {/* form */}
  308. <Form
  309. style={{
  310. "--border-bottom": "none",
  311. "--border-top": "none",
  312. "--border-inner": "none",
  313. }}
  314. ref={formRef}
  315. onFinish={onFinish}
  316. initialValues={initParams}
  317. disabled={!activeWallet.channels}
  318. hasFeedback={!!activeWallet.channels}
  319. className={"mt-[0.1rem]"}
  320. footer={<ButtonOwn active>{t("WithdrawPage.Saque")}</ButtonOwn>}
  321. >
  322. {isStrictMode ? (
  323. <>
  324. <Form.Item
  325. name="user_name"
  326. label=""
  327. rules={[
  328. { required: true, message: t("WithdrawPage.usernameReg") },
  329. ]}
  330. >
  331. <Input placeholder={t("WithdrawPage.username")} />
  332. </Form.Item>
  333. <Form.Item
  334. name="passport"
  335. label=""
  336. rules={[
  337. {
  338. required: true,
  339. message: t("WithdrawPage.cpfReg"),
  340. min: 6,
  341. max: 20,
  342. },
  343. ]}
  344. >
  345. <Input
  346. placeholder={t("WithdrawPage.cpf")}
  347. maxLength={20}
  348. type={"text"}
  349. />
  350. </Form.Item>
  351. </>
  352. ) : null}
  353. <p className={"my-[0.1rem]"}>{t("WithdrawPage.Tipo")}</p>
  354. {activeWallet.channels?.length && (
  355. <Form.Item
  356. name="channel"
  357. label=""
  358. rules={[{ validator: ChannelValidator }]}
  359. >
  360. <PixField actions={walletAction || []} />
  361. </Form.Item>
  362. )}
  363. <h1>{t("WithdrawPage.Vincule")}</h1>
  364. <p className={"my-[0.1rem]"}>{t("WithdrawPage.Montante")} (BRL):</p>
  365. <Form.Item
  366. name="amount"
  367. label=""
  368. rules={[
  369. { required: true, message: t("WithdrawPage.amount") },
  370. {
  371. type: "number",
  372. validator: AmountValidator,
  373. },
  374. ]}
  375. >
  376. <Input
  377. placeholder={`Mín. ${activeWallet.min_amount || 0}`}
  378. type={"number"}
  379. min={activeWallet.min_amount}
  380. />
  381. </Form.Item>
  382. <ul className="ul-box">
  383. <li>
  384. {t("WithdrawPage.SaqueDisponivel")}{" "}
  385. <span className="tip">
  386. {/* 如果当前现金打码量等于当前目标打码量 则 可以提现, 如果当前打码量=== 100% 则可以提现 */}
  387. {flatPoint(
  388. wallet.target_score_rollover - wallet.current_score_rollover
  389. ) === 0 ||
  390. percentage(
  391. wallet.current_score_rollover,
  392. wallet.target_score_rollover
  393. ) >= 100
  394. ? wallet.score
  395. : 0}
  396. BRL
  397. </span>
  398. <span
  399. className="iconfont icon-iconhelp"
  400. onClick={() => scoreRef.current?.onOpen()}
  401. ></span>
  402. </li>
  403. <li>
  404. {t("WithdrawPage.Valor")}
  405. <span className="tip">
  406. {flatPoint(
  407. wallet.target_score_rollover - wallet.current_score_rollover
  408. ) === 0 ||
  409. percentage(
  410. wallet.current_score_rollover,
  411. wallet.target_score_rollover
  412. ) >= 100
  413. ? flatPoint((wallet.score || 0) + wallet.point)
  414. : 0}
  415. BRL
  416. </span>
  417. <span
  418. className="iconfont icon-iconhelp"
  419. onClick={() => withdrawRef.current?.onOpen()}
  420. ></span>
  421. </li>
  422. <li>
  423. {t("WithdrawPage.Para")},{" "}
  424. <Link href="/" className="toHome router-link-active" replace>
  425. {t("WithdrawPage.Aposte")}
  426. </Link>
  427. </li>
  428. </ul>
  429. </Form>
  430. </div>
  431. </Box>
  432. {/*本金*/}
  433. <TipsModal
  434. title={
  435. <div className={"flex items-center"}>
  436. <i
  437. className={"iconfont icon-liwuhuodong mr-[0.0694rem] text-[0.2778rem]"}
  438. ></i>
  439. SACAR
  440. {t("WithdrawPage.scoreTitle")}
  441. </div>
  442. }
  443. ref={scoreRef}
  444. >
  445. <ul>
  446. <li className={"mb-[0.0694rem]"}>
  447. <span>{t("WithdrawPage.scoreTips")}</span>
  448. <span>{wallet.score}</span>
  449. </li>
  450. <li>
  451. <div className={"flex items-center"}>
  452. <ProgressBar
  453. percent={percentage(
  454. wallet.current_score_rollover,
  455. wallet.target_score_rollover
  456. )}
  457. className={"mr-[0.0694rem] flex-1"}
  458. style={{
  459. "--fill-color": "#fb8b05",
  460. "--track-width": "0.0694rem",
  461. }}
  462. />
  463. <span>
  464. {percentage(
  465. wallet.current_score_rollover,
  466. wallet.target_score_rollover
  467. )}
  468. %
  469. </span>
  470. </div>
  471. <div>
  472. <span>{t("WithdrawPage.pointBet")}</span>
  473. <span>
  474. {flatPoint(
  475. wallet.target_score_rollover - wallet.current_score_rollover
  476. )}
  477. </span>
  478. </div>
  479. </li>
  480. </ul>
  481. </TipsModal>
  482. {/*彩金*/}
  483. <TipsModal
  484. title={
  485. <div className={"flex items-center"}>
  486. <i
  487. className={"iconfont icon-liwuhuodong mr-[0.0694rem] text-[0.2778rem]"}
  488. ></i>
  489. {t("WithdrawPage.pointTitle")}
  490. </div>
  491. }
  492. ref={withdrawRef}
  493. >
  494. <ul>
  495. <li className={"mb-[0.0694rem]"}>
  496. <span>{t("WithdrawPage.pointTips")}</span>
  497. <span>{wallet.point}</span>
  498. </li>
  499. <li>
  500. <div className={"flex items-center"}>
  501. <ProgressBar
  502. percent={percentage(
  503. wallet.current_point_rollover,
  504. wallet.target_point_rollover
  505. )}
  506. className={"mr-[0.0694rem] flex-1"}
  507. style={{
  508. "--fill-color": "#fb8b05",
  509. "--track-width": "0.0694rem",
  510. }}
  511. />
  512. <span>
  513. {percentage(
  514. wallet.current_point_rollover,
  515. wallet.target_point_rollover
  516. )}
  517. %
  518. </span>
  519. </div>
  520. <div>
  521. <span>{t("WithdrawPage.pointBet")}</span>
  522. <span>
  523. {flatPoint(
  524. wallet.target_point_rollover - wallet.current_point_rollover
  525. )}
  526. </span>
  527. </div>
  528. </li>
  529. </ul>
  530. </TipsModal>
  531. {/*含有彩金提现拦截*/}
  532. <TipsModal
  533. title={
  534. <div className={"text-left text-[0.12rem] font-medium text-[#666]"}>
  535. Retirar bem sucedido, seu dinheiro de prémio será limpo. Você tem certeza?
  536. </div>
  537. }
  538. ref={bounsModalRef}
  539. >
  540. <ul>
  541. <li className={"mb-[0.0694rem]"}>
  542. <span>{t("WithdrawPage.pointTips")}</span>
  543. <span>{wallet.point}</span>
  544. </li>
  545. <li>
  546. <div className={"flex items-center"}>
  547. <ProgressBar
  548. percent={percentage(
  549. wallet.current_point_rollover,
  550. wallet.target_point_rollover
  551. )}
  552. className={"mr-[0.0694rem] flex-1"}
  553. style={{
  554. "--fill-color": "#fb8b05",
  555. "--track-width": "0.0694rem",
  556. }}
  557. />
  558. <span>
  559. {percentage(
  560. wallet.current_point_rollover,
  561. wallet.target_point_rollover
  562. )}
  563. %
  564. </span>
  565. </div>
  566. <div>
  567. <span>{t("WithdrawPage.pointBet")}</span>
  568. <span>
  569. {flatPoint(
  570. wallet.target_point_rollover - wallet.current_point_rollover
  571. )}
  572. </span>
  573. </div>
  574. <div className={"mt-[20px] flex justify-around"}>
  575. <Button
  576. color={"default"}
  577. fill={"none"}
  578. style={{
  579. "--text-color": "var(--primary-color)",
  580. }}
  581. onClick={() => {
  582. bounsModalRef.current?.onClose();
  583. }}
  584. >
  585. cancelar
  586. </Button>
  587. <Button
  588. color={"primary"}
  589. className={"ml-[30px]"}
  590. style={{
  591. "--background-color": "var(--primary-color)",
  592. "--border-color": "var(--primary-color)",
  593. }}
  594. onClick={() => {
  595. // 关闭彩金弹窗
  596. bounsModalRef.current?.onClose();
  597. extractHandler(paramsTarget.current!);
  598. }}
  599. >
  600. confirmação
  601. </Button>
  602. </div>
  603. </li>
  604. </ul>
  605. </TipsModal>
  606. {/* 提现拦截 */}
  607. <TipsModal title={"Tips"} ref={gameModelRef}>
  608. <p className={"text-left text-[0.12rem] font-medium text-[#666]"}>
  609. Atualmente, existem jogos de bônus inacabados que não podem iniciar saques.
  610. </p>
  611. <div className={"mt-[0.0694rem] flex justify-center"}>
  612. <Button
  613. color={"primary"}
  614. className={"mx-auto"}
  615. style={{
  616. "--background-color": "var(--primary-color)",
  617. "--border-color": "var(--primary-color)",
  618. }}
  619. onClick={goGame}
  620. >
  621. para jogos
  622. </Button>
  623. </div>
  624. </TipsModal>
  625. </>
  626. );
  627. };
  628. export default WithdrawWidget;